﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace ITAP.Core.Calculations.Search
{
    public delegate double DoubleValueProviderDelegate(double arg);
    public class SearchTree
    {
        private int intervalsCount;
        private double minFragmentLength;
        private double minFunctionFragmentLength;
        private double startValue;
        private double endValue;
        private DoubleValueProviderDelegate doubleValueProvider;

        private SearchTreeNode rootNode;

        public SearchTree(double startValue, double endValue, DoubleValueProviderDelegate doubleValueProvider, int intervalsCount, double minFragmentLength, double minFunctionFragmentLength)
        {
            this.rootNode = new SearchTreeNode(null, double.NaN, 0);
            this.doubleValueProvider = doubleValueProvider;
            this.intervalsCount = intervalsCount;
            this.Initialize(startValue, endValue, minFragmentLength);
        }

        private void Initialize(double startValue, double endValue, double minFragmentLength)
        {
            this.startValue = startValue;
            this.endValue = endValue;
            this.Clear();
            rootNode = new SearchTreeNode(null, double.NaN, 0);
            this.minFragmentLength = minFragmentLength;
            this.FillSegment(startValue, endValue, rootNode, 1);
            this.ChildsToArrays(this.rootNode);
        }

        private void ChildsToArrays(SearchTreeNode node)
        {
            node.ChildsArray = node.Childs.ToArray();
            node.Childs.Clear();
            foreach (SearchTreeNode _node in node.ChildsArray)
            {
                this.ChildsToArrays(_node);
            }
        }

        private void FillSegment(double startValue, double endValue, SearchTreeNode currentNode, int level)
        {
            double offset = (endValue - startValue) / intervalsCount;

            double funcOffset = Math.Abs(doubleValueProvider.Invoke(startValue) - doubleValueProvider.Invoke(endValue));

            if (offset < minFragmentLength || funcOffset < minFunctionFragmentLength)
                return;

            for (int i = 0; i <= intervalsCount; i++)
            {
                SearchTreeNode newNode = new SearchTreeNode(currentNode, doubleValueProvider.Invoke(startValue + offset * i), level);
                currentNode.Childs.Add(newNode);
                this.FillSegment(startValue + offset * i, startValue + offset * (i + 1), newNode, level + 1);
            }
        }

        private bool isFind = false;

        private double SearchRecursive(double startValue, double endValue, SearchTreeNode currentNode, double value)
        {
            double offset = (endValue - startValue) / (double)intervalsCount;
            double _value = value - startValue;
            int index = (int)(_value / offset);

            double start = startValue + offset * index;
            double end = startValue + offset * (index + 1);

            if (currentNode.ChildsArray.Length == 0)
                return currentNode.Value;

            double fst = SearchRecursive(start, end, currentNode.ChildsArray[index], value);

            if (isFind)
                return fst;

            double sec = SearchRecursive(start, end, currentNode.ChildsArray[index + 1], value);
            double fstDistance = value - start;
            double secDistance = end - value;
            if (fstDistance < secDistance)
            {
                isFind = true;
                return fst;
            }
            else
            {
                isFind = true;
                return sec;
            }
        }

        public double Search(double value)
        {
            double result;
            isFind = false;
            result = this.SearchRecursive(this.startValue, this.endValue, rootNode, value);
            return result;
        }

        public void Clear()
        {
            rootNode.Clear();
        }
    }
}
